<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>打开控制台查看结果</div>
    <script>
        console.log('============指数运算符===========');
        /* 指数运算符
           指数运算符是右结合，多个指数运算符连用时，从右边开始计算
        */
       // 2 * 2
       console.log(2 ** 2); // 4

       // 2 * 2 * 2 * 2
       console.log(2 ** 4); // 16


       // 2 ** (3 ** 2)
       // 2 ** 9
       // 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2
       console.log(2 ** 3 ** 2);  // 512

       /* 指数运算符可以与等号结合，变成赋值运算符 （**=） */
       let a = 2
       a **= 4
       console.log(a);  // 16
       // 这里相当于是 a = 2 ** 4

       console.log('===============链判断运算符=============');
       /* 
         在实际项目中，经常需要获取到对象内的某个属性，该属性有可能是不存在的，所以需要判断一下。
         在读取对象的深层属性时，安全的写法需要做层层判断
       */
       let data = {
          user:{
            goods : {
                num : 100
            }
          }
       }
       // 错误写法 =》 不安全，因为直接读取内部深层的值，前面的值（user、goods)一旦不存在就会报错
       let num = data.user.goods.num || 0
       console.log(num);
       
       // 正确的写法，层层判断，data存在，并且data.user存在....其中有一项不存在则返回0
       let num2 = data && data.user && data.user.goods && data.user.goods.num || 0
       console.log(num2);

       // 三元表达式 ?: 正确写法，层层判断，直到找到最后一层
       let num3 = data ? data.user ? data.user.goods ? data.user.goods.num ? data.user.goods.num : 0 : '暂无商品' : '用户不存在' : []
       console.log(num3);

       // 但是这种写法非常麻烦，需要层层判断，因此es2020引入了"链判断运算符"，语法： ?. 用来简化上面的写法
       let num4 = data?.user?.goods?.num || 0
       console.log(num4);

       // 链判断运算符可以判断对象内的函数是否存在，如果存在就立即执行，不存在则返回undefined
       let data2 = {
        chains(){ return '存在'}
       }
      console.log(data2.chains?.());   // 存在
      console.log(data2.chains2?.());  // undefined

      /* 注意：普通函数是无法通过链判断运算符判断是否存在的，如果不存在会直接报错 */
      function chains(){
        return '存在'
      }
      console.log(chains?.());  // 存在
      // console.log(chains2?.()); // 报错

      let w = 'hello'
      let data3 = {
        [w] : 'world',
        name : "东方不败",
        sayHi(){ return 'hello' }
      }
      console.log(data3?.[w] || 'default');  // world
      console.log(data3?.name || 'default'); // 东方不败
      console.log(data3.sayHi?.());  // hello
      console.log(data3.sayHi2?.()); // undefined
      console.log(data3?.sayHi?.());  // hello

      /* 链判断运算符其实有一个短路机制，如果条件不满足则终止执行 */

      /* 以下场合会报错
         1、构造函数
         2、链判断运算符右侧有模板字符串
         3、链判断运算符左侧是 super
         4、链运算符用于赋值运算符左侧
      */
    // 构造函数 报错
    //  new a?.()

    // 右侧有模板字符串
    // a?.`${w}`
    
    // 左侧是super
    // super?.()

    // 右侧有赋值
    // a?.b = 100

    /* 注意：链运算符右侧如果是数字将会失效，会被解析成三元运算符，.会被解析成数字小数点 */
    let num5 = {
        1 : 100
    }
    console.log(.123);  // 0.123
    console.log(num5?.1 : undefined);  // 这里实际上被解析为num5 ? .1 : undefined,变成了三元运算符


    console.log('================Null判断运算符=============');
    /* 在读取对象属性的时候，判断某个属性是否存在，如果属性的值是null或undefined时,给该属性一个默认值，常见的做法是用||运算符判断是否存在，不存在则返回右侧的值
    */
    /* || 运算符存在隐式转换，0、null、undefined、空字符串都会被转换为false */
   let n = {
       user : '东方不败',
       status : 0
   }
   let u = n.user || '用户不存在'
   let u2 = n.user2 || '用户不存在'
   console.log(u);  // 东方不败
   console.log(u2); // 用户不存在
 
   let u3 = n.status || '不存在'
   console.log(u3);  // 不存在
   // ||运算符更适合判断属性是否为假（false），而不是null、undefined

   /* es2020提供了新的null判断运算符 ?? 
      它的行为跟 || 运算符基本相同，唯一的不同是它没有隐式转换，只有左侧的值为null或undefined时才会返回右侧的值
   */
  let judge = {
    user : "东方不败",
    num : 0,
    status : false,
    em : null
  }
  let back = judge.user ?? '不存在'
  let back2 = judge.num ?? '不存在'
  let back3 = judge.status ?? '不存在'
  let back4 = judge.em ?? '不存在'
  console.log(back); // 东方不败
  console.log(back2); // 0
  console.log(back3); // false
  console.log(back4); // 不存在

  /* 多个逻辑运算符一起使用，必须用括号表明优先级，否则报错 */
    // 报错
    /* 
    a && b ?? c
    a ?? b && c
    a || b ?? c
    a ?? b || c
    */
   // 正确的写法， 加上括号表明优先级
   /* 
   (a && b) ?? c;
    a && (b ?? c);

    (a ?? b) && c;
    a ?? (b && c);

    (a || b) ?? c;
    a || (b ?? c);

    (a ?? b) || c;
    a ?? (b || c); 
    */

    console.log('============逻辑赋值运算符============');
    /* es2021引入三个逻辑赋值运算符，将逻辑运算符和赋值运算符结合 */
    let x = 0
    let y = 5
    let z = x ||= y
    let z2 = x ??= y
    let z3 = x &&= y

    console.log(z); // 5
    console.log(z2); // 5
    console.log(z3); // 5
    console.log(x); // 5

    /* 
    其实这个逻辑赋值运算符和逻辑运算符是一样的，只不过看起来更直观
    逻辑运算符：左侧的值不存在就把右侧的值赋值给该对象
    逻辑赋值运算符：左侧的值不存在就把右侧的值等于左侧，将左侧的值赋值给该对象
    */

   /* 注意：逻辑赋值运算符会更改被赋值的原始值，上述案例中，右侧的y赋值给了左侧的x，左侧的x=5，所以下面的??运算符判断x=5，导致并没有走右侧的y逻辑赋值运算 */
   let b = 0
   let m = 5
   let f = b ??= m
   console.log(f);  // 0


    </script>
</body>
</html>